import * as zlib from 'zlib'
import { IncomingMessage, ServerResponse } from "http"
import { Config, LoginStateItem, RequestWith, Store } from '../interface'
import { PLACE_HOLDER, Cookie, AUTH_KEY, getClientIP, generateToken, atob, getLoginState } from '../utils'
import { createSession, getSession } from '../Session'
import { F2EConfig } from 'f2e-server'
import { ConfigObject, create as createCaptcha } from 'svg-captcha'

/**
 * 账户提交加密算法
 * @param account 
 * @param token 
 */
const decrypt_account = (account: string, token: string) => {
    if (!account || !token) return null
    var bits = new Set(token.substring(4, 12).split('').map(n => parseInt(n, 16)));
    const chars = []
    for (let i = 0; i < account.length; i++) {
        if (!bits.has(i)) {
            chars.push(account[i])
        }
    }
    try {
        const { name, password = '' } = JSON.parse(atob(chars.join('')))
        return { name, password }
    } catch (e) {
        return null
    }
}


export const captcha = (login_captcha: ConfigObject, renderHeaders: F2EConfig['renderHeaders'] = (h => h)) => {

    return (req: IncomingMessage, resp: ServerResponse) => {
        const state: LoginStateItem = getLoginState(req);
        const captcha = createCaptcha(login_captcha)
        resp.writeHead(200, renderHeaders({
            'Content-Type': 'image/svg+xml'
        }))
        resp.end(captcha.data)
        if (state) {
            state.captcha_text = captcha.text
        }
    }

}

export const login = ({
    get_login_page, login_page, onLogin, onSessionChange, maxAge, error_message, admin_url,
    login_error_times = 5, login_error_remain = 60000,
    login_captcha,
    with_login_search,
    login_execute,
    isJsonOut,
    onGetCrsfToken,
}: Config, store: Store, getLayoutPage: () => string, renderHeaders: F2EConfig['renderHeaders'] = (h => h)) => {
    const renderLoginPage = (param: any) => {
        return getLayoutPage().replace(PLACE_HOLDER, get_login_page ? get_login_page() : login_page).replace(/\{\{(\w+)\}\}/g, (a, w) => {
            return param[w.trim()] || ''
        })
    }

    const run = async (req: IncomingMessage, resp: ServerResponse) => {
        const IP = getClientIP(req)
        const state = getLoginState(req)

        const query = with_login_search && await with_login_search(req) || '/'
        if (req.method.toUpperCase() === 'GET') {
            let info: any = {
                admin_url,
                query, crsf_token: state.crsf_token
            }
            if (login_captcha) {
                info.captcha_url = `/${admin_url}/captcha`
            }
            if (isJsonOut(req as any)) {
                resp.writeHead(200, renderHeaders({ 'Content-Type': 'application/json; charset=utf-8' }, req))
                if (onGetCrsfToken) {
                    try {
                        info.crsf_token = await onGetCrsfToken(req as any, info.crsf_token)
                    } catch (error) {
                        info = { query, error_count: ++state.error_count, error: error.toString() }
                    }
                }
                resp.end(JSON.stringify(info))
            } else {
                resp.writeHead(200, renderHeaders({
                    'Content-Encoding': 'gzip',
                    'Content-Type': 'text/html; charset=utf-8'
                }, req))
                resp.end(zlib.gzipSync(renderLoginPage({...info, captcha_display: login_captcha ? 'flex' : 'none',})))
            }
            return false
        }
        const { name, password = '', account, crsf_token, captcha } = req['post'] || {}
        const key = store.createToken(decrypt_account(account, crsf_token) || { name, password })
        let user = await store.lookLoginUser(key)
        let error: string
        if (state.error_count >= login_error_times) {
            error = error_message.account_lock
            clearTimeout(state.clear_timer)
            state.clear_timer = setTimeout(() => {
                state.error_count = 0
            }, login_error_remain);
        } else if (crsf_token !== state.crsf_token) {
            state.crsf_token = generateToken()
            error = error_message.no_token
        } else if (login_captcha && captcha != state.captcha_text) {
            error = error_message.wrong_captcha
        } else if (!user) {
            error = error_message.wrong_pass
        } else if (user && user.expires && user.expires < Date.now()) {
            error = error_message.expires
        } else if (user && user.lock_ips && user.lock_ips.length && !user.lock_ips.includes(getClientIP(req))) {
            error = error_message.lock_ips
        } else if (user && !user.role) {
            error = error_message.no_role
        }

        if (error) {
            state.error_count++;
            let error_info = { query, error_count: state.error_count, error }
            if (isJsonOut(req as any)) {
                resp.writeHead(200, renderHeaders({ 'Content-Type': 'application/json; charset=utf-8' }, req))
                resp.end(JSON.stringify(error_info))
            } else {
                resp.writeHead(200, renderHeaders({
                    'Content-Encoding': 'gzip',
                    'Content-Type': 'text/html; charset=utf-8'
                }, req))
                resp.end(zlib.gzipSync(renderLoginPage(error_info)))
            }
            return false
        }

        Object.assign(user, { login_ip: IP })
        Object.assign(req, {
            loginUser: user
        })
        if (onLogin) {
            try {
                await onLogin(<any>req)
            } catch (error) {
                if (error) {
                    state.error_count++;
                    let error_info = { query, error_count: state.error_count, error: error.toString() }
                    if (isJsonOut(req as any)) {
                        resp.writeHead(200, renderHeaders({ 'Content-Type': 'application/json; charset=utf-8' }, req))
                        resp.end(JSON.stringify(error_info))
                    } else {
                        resp.writeHead(200, renderHeaders({
                            'Content-Encoding': 'gzip',
                            'Content-Type': 'text/html; charset=utf-8'
                        }, req))
                        resp.end(zlib.gzipSync(renderLoginPage(error_info)))
                    }
                    return false
                }
            }
        }

        const { host = '' } = req.headers
        const last_session = getSession(key) || { ip: IP }
        resp.setHeader('Set-Cookie', Cookie.stringify(host.replace(/[^:]+/, AUTH_KEY), createSession({
            token: key,
            ip: IP,
            expire: Date.now() + maxAge * 1000,
        }, onSessionChange), { maxAge, httpOnly: true }))
        if (isJsonOut(req as any)) {
            const session = getSession(user.token)
            resp.writeHead(200, renderHeaders({ 'Content-Type': 'application/json; charset=utf-8' }, req))
            resp.end(JSON.stringify({
                success: true,
                user: {
                    ...user,
                    token: state.crsf_token,
                    access_token: session.session,
                    password: '',
                },
                last_ip: last_session.ip,
                msg: user.login_ip != last_session.ip ? error_message.ip_change : undefined
            }))
        } else {
            resp.writeHead(302, renderHeaders({
                location: query
            }, req))
            resp.end()
        }
    }
    return (req: RequestWith, resp: ServerResponse) => {
        (login_execute || run)(req, resp)
        return false;
    }
}
